
#ifndef __BMANX_BODY2__HEADER_FILE__
#define __BMANX_BODY2__HEADER_FILE__

#include "coid/comm/commtypes.h"
#include "../rsp/rsp.h"

using namespace coid;


////////////////////////////////////////////////////////////////////////////////
struct coord
{
    int val;

    coord operator - ( coord x ) const      { return coord( val - x.val ); }
    coord operator + ( coord x ) const      { return coord( val + x.val ); }
    
    coord operator * ( coord x ) const      { return coord( ((int64)val * x.val) >> 16 ); }
    coord operator * ( int i ) const        { return coord( val * i ); }
    coord operator * ( uint i ) const       { return coord( (int) (val * i) ); }

    coord operator -= ( coord x )           { val -= x.val;  return *this; }
    coord operator += ( coord x )           { val += x.val;  return *this; }
    coord operator *= ( coord x )           { val = (int) (((int64)val * x.val) >> 16 );  return *this; }

    coord operator >> ( int n ) const       { return coord( val >> n ); }
    coord operator << ( int n ) const       { return coord( val << n ); }

    coord operator >>= ( int n )            { val >>= n;  return *this; }
    coord operator <<= ( int n )            { val <<= n;  return *this; }

    bool operator < ( coord x ) const       { return val < x.val; }
    bool operator <= ( coord x ) const      { return val <= x.val; }
    bool operator > ( coord x ) const       { return val > x.val; }
    bool operator >= ( coord x ) const      { return val >= x.val; }

    bool operator == ( coord x ) const      { return val == x.val; }
    bool operator != ( coord x ) const      { return val != x.val; }
    bool operator == ( int i ) const        { return val == i; }
    bool operator != ( int i ) const        { return val != i; }

    coord operator - () const               { return coord(-val); }

    uint umod( uint d ) const               { int m = val%d;  return m<0  ?  d+m  :  m; }

    coord& mult( int f )
    {
        val = (int) (((int64)val * f) >> 16);
        return *this;
    }

    coord square() const                    { return coord( ((int64)val * val) >> 16 ); }

    coord abs() const                       { return coord( (int)int_abs(val) ); }

/*
    coord fast_invsqrt() const
    {
        float x = val / 65536.0;
        float xhalf = 0.5f*x;
        int i = *(int*)&x;
        i = 0x5f3759df - (i >> 1); // This line hides a LOT of math!
        x = *(float*)&i;
        x = x*(1.5f - xhalf*x*x); // repeat this statement for a better approximation

        int v;
        float fx = floorf( x * 65536.0 );
	    _asm fld fx
	    _asm fistp v

        return coord(v);
    }
*/

    explicit coord( int v ) : val(v) {}
    explicit coord( int64 v ) : val((int)v) {}

    coord() {}

    coord( const coord& x ) : val(x.val)    {}

    coord operator = ( coord x )            { val = x.val;  return *this; }
    coord operator = ( int i )              { val = i;  return *this; }

    int as_int() const                      { return val; }
    //operator int () const                   { return val; }

    int whole() const                       { return val >> 16; }
    uint fraction() const                   { return val & 0x0000ffff; }

    coord trunc() const                     { return coord( val & (int)0xffff0000 ); }
};

////
struct coord4
{
    coord x,y,z;
    int d;
};


////////////////////////////////////////////////////////////////////////////////
struct dir
{
    enum {
        fEAST               = 1,
        fWEST               = 2,
        fNORTH              = 4,
        fSOUTH              = 8,
        xWE                 = fEAST | fWEST,
        xSN                 = fSOUTH | fNORTH,

        xDIR                = 0x0f,

        EAST                = fEAST,
        NORTHEAST           = fEAST | fNORTH,
        NORTH               = fNORTH,
        NORTHWEST           = fNORTH | fWEST,
        WEST                = fWEST,
        SOUTHWEST           = fWEST | fSOUTH,
        SOUTH               = fSOUTH,
        SOUTHEAST           = fSOUTH | fEAST,

        ENUM_INT            = 0x77777777,
    };

    static int delta_x( uint dir )      { return (dir & fEAST) ? +1 : ( (dir & fWEST) ? -1 : 0 ); }
    static int delta_y( uint dir )      { return (dir & fNORTH) ? +1 : ( (dir & fSOUTH) ? -1 : 0 ); }
};

////////////////////////////////////////////////////////////////////////////////
class collisionbody
{
    coord   _radius;            ///< bounding circle radius
    coord   _halfdead;          ///< half the size of dead band
    uint    _flags;             ///<

public:
    enum {
        fSQUARE                 = 1,    ///< square collision body
        fNOSELFCOLLIDE          = 2,    ///< won't collide with objects that have this flag set
    };

    bool is_square() const              { return (_flags & fSQUARE) != 0; }
    bool is_noselfcollide() const       { return (_flags & fNOSELFCOLLIDE) != 0; }

    coord get_halfdead() const          { return _halfdead; }

    coord get_radius() const            { return _radius; }


    collisionbody( coord rad, coord dead, uint flg )
    {
        _flags = flg;
        if( flg & fSQUARE )
            rad.mult(92682);
        _radius = rad;
        _halfdead = dead >> 1;
    }

    bool collides( const coord4& pa, const collisionbody& b, const coord4& pb ) const
    {
        if( is_noselfcollide() && b.is_noselfcollide() )
            return false;
        coord dist = (pa.x - pb.x)*(pa.x - pb.x) + (pa.y - pb.y)*(pa.y - pb.y);
        coord sqrad = _radius + b._radius;
        sqrad *= sqrad;
        if( dist >= sqrad )
            return false;
        //if( dist < sqrad/2 )
        //    return true;

        //circle-circle
        if( !is_square()  &&  !b.is_square() )
            return true;
        //circle-square
        if( is_square()  &&  !b.is_square() )
            return b.circle_collides_square( pb, *this, pa, 0 );
        if( !is_square()  &&  b.is_square() )
            return circle_collides_square( pa, b, pb, 0 );

        //square-square -- we don't need, fake it
        return true;
    }

    bool collides( const coord4& pa, const collisionbody& b, const coord4& pb, int* da ) const
    {
        if( is_noselfcollide() && b.is_noselfcollide() )
            return false;
        coord dist = (pa.x - pb.x)*(pa.x - pb.x) + (pa.y - pb.y)*(pa.y - pb.y);
        coord sqrad = _radius + b._radius;
        sqrad *= sqrad;
        if( dist >= sqrad )
            return false;
        //if( dist < sqrad/2 )
        //    return true;

        //circle-circle
        if( !is_square()  &&  !b.is_square() )
        {
            if(da)
            {
                if( *da & dir::xSN )
                {
                    if( pa.x < pb.x - b.get_halfdead() )
                        da[1] = dir::fWEST;// | da[0];
                    else if( pa.x > pb.x + b.get_halfdead() )
                        da[1] = dir::fEAST;// | da[0];
                    else
                        da[1] = 0;
                }
                else
                {
                    if( pa.y < pb.y - b.get_halfdead() )
                        da[1] = dir::fSOUTH;// | da[0];
                    else if( pa.y > pb.y + b.get_halfdead() )
                        da[1] = dir::fNORTH;// | da[0];
                    else
                        da[1] = 0;
                }
            }

            return true;
        }
        //circle-square
        if( is_square()  &&  !b.is_square() )
            return b.circle_collides_square( pb, *this, pa, da );
        if( !is_square()  &&  b.is_square() )
            return circle_collides_square( pa, b, pb, da );

        //square-square -- we don't need, fake it
        return true;
    }

    bool circle_collides_square( const coord4& pa, const collisionbody& b, const coord4& pb, int* da ) const
    {
        coord sqs = b._radius;
        sqs.mult(46341);    // 1/sqrt(2)

        coord dsq = (_radius+sqs).square();

        coord x1 = pb.x - sqs;
        coord x2 = pb.x + sqs;
        if( pa.x <= x2  &&  pa.x >= x1 )
        {
            if( (pa.y - pb.y)*(pa.y - pb.y) < dsq )
            {
                //north or south side
                if(da)
                {
                    if( da[0] & dir::xWE )
                    {
                        if( pa.y < pb.y )
                            da[1] = dir::fSOUTH;
                        else if( pa.y > pb.y )
                            da[1] = dir::fNORTH;
                        else
                            da[1] = 0;
                    }
                    else
                    {
                        if( pa.x < pb.x - b.get_halfdead() )
                            da[1] = dir::fWEST;
                        else if( pa.x > pb.x + b.get_halfdead() )
                            da[1] = dir::fEAST;
                        else
                            da[1] = 0;
                    }
                }
                return true;
            }
            return false;
        }

        coord y1 = pb.y - sqs;
        coord y2 = pb.y + sqs;
        if( pa.y <= y2  &&  pa.y >= y1 )
        {
            //east or west side
            if( (pa.x - pb.x)*(pa.x - pb.x) < dsq )
            {
                if(da)
                {
                    if( da[0] & dir::xSN )
                    {
                        if( pa.x < pb.x )
                            da[1] = dir::fWEST;
                        else if( pa.x > pb.x )
                            da[1] = dir::fEAST;
                        else
                            da[1] = 0;
                    }
                    else
                    {
                        if( pa.y < pb.y - b.get_halfdead() )
                            da[1] = dir::fSOUTH;
                        else if( pa.y > pb.y + b.get_halfdead() )
                            da[1] = dir::fNORTH;
                        else
                            da[1] = 0;
                    }
                }
                return true;
            }
            return false;
        }


        if( pa.x > x2 )
        {
            if( pa.y > y2 )
            {
                //northeast corner
                if( (pa.x-x2)*(pa.x-x2) + (pa.y-y2)*(pa.y-y2) < _radius*_radius )
                {
                    if(da)
                    {
                        if( da[0] & dir::fWEST )        da[1] = dir::fNORTH;
                        else if( da[0] & dir::fSOUTH )  da[1] = dir::fEAST;
                        else da[1] = 0;
                    }
                    return true;
                }
            }
            else
            {
                //southeast corner
                if( (pa.x-x2)*(pa.x-x2) + (pa.y-y1)*(pa.y-y1) < _radius*_radius )
                {
                    if(da)
                    {
                        if( da[0] & dir::fWEST )        da[1] = dir::fSOUTH;
                        else if( da[0] & dir::fNORTH )  da[1] = dir::fEAST;
                        else da[1] = 0;
                    }
                    return true;
                }
            }
        }
        else
        {
            if( pa.y > y2 )
            {
                //northwest
                if( (pa.x-x1)*(pa.x-x1) + (pa.y-y2)*(pa.y-y2) < _radius*_radius )
                {
                    if(da)
                    {
                        if( da[0] & dir::fEAST )        da[1] = dir::fNORTH;
                        else if( da[0] & dir::fSOUTH )  da[1] = dir::fWEST;
                        else da[1] = 0;
                    }
                    return true;
                }
            }
            else
            {
                //southwest
                if( (pa.x-x1)*(pa.x-x1) + (pa.y-y1)*(pa.y-y1) < _radius*_radius )
                {
                    if(da)
                    {
                        if( da[0] & dir::fEAST )        da[1] = dir::fSOUTH;
                        else if( da[0] & dir::fNORTH )  da[1] = dir::fWEST;
                        else da[1] = 0;
                    }
                    return true;
                }
            }
        }
        return false;
    }

};

////////////////////////////////////////////////////////////////////////////////
///a body base class
/**
    body is an entity class that describes attributes of a closed volume object in
    relation to the world
*/
class body : public rsp_base
{
public:

    uint get_direction() const          { return _xyzd0.d; }

    bool collides_body( const body& b, int* dire ) const
    {
        if( &b == this )
            return false;   // doesn't collide with itself
        if( _xyzd0.z != 0  ||  b._xyzd0.z != 0 )
            return false;   //anything in air or underground (dead) doesn't collide

        return _coll->collides( _xyzd0, *b._coll, b._xyzd0, dire );
    }

    bool collides_body( const collisionbody& b, const coord4& pb, int* dire ) const
    {
        return _coll->collides( _xyzd0, b, pb, dire );
    }

    coord distance_sq( const body& b ) const
    {
        return (_xyzd0.x - b._xyzd0.x)*(_xyzd0.x - b._xyzd0.x)
            + (_xyzd0.y - b._xyzd0.y)*(_xyzd0.y - b._xyzd0.y);
    }

    bool on_ground() const              { return _xyzd0.z <= coord(0); }


    int get_tile_x() const              { return _xyzd0.x.whole(); }
    int get_tile_y() const              { return _xyzd0.y.whole(); }


    bool on_same_tile( const body& b ) const
    { return _xyzd0.x.whole() == b._xyzd0.x.whole()  &&  _xyzd0.y.whole() == b._xyzd0.y.whole(); }

    body( const collisionbody* coll ) : _coll(coll)
    {
        _xyzd0.d = dir::fSOUTH;
    }

    coord4  _xyzd0;             ///< position of reference point and orientation
    coord4  _xyzd1;             ///< first time derivative of position

    const collisionbody* _coll;


};


#endif //__BMANX_BODY2__HEADER_FILE__
